import {PlainObject,TreeNodeWrap,TreeNodeCallback,PromiseMap,ActionMap,WidthLimit} from '../../../types';
import {MySet} from '../polyfill';
import Vue from 'vue';
/**
 * 判断普通对象(不包括数组)
 * 除了isBaseType判断为true和数组的值
 * @param val 测试值
 * @return 测试结果
 */
function isObject(val:any):boolean{
	return !(isBaseType(val)||Array.isArray(val));
}
/**
 * 判断基本类型(可以直接赋值)
 * null, undefined, boolean, number, string, function, bigint, symbol皆属于基本类型, 可以直接赋值
 * @param val 测试值
 * @return 测试结果
 */
function isBaseType(val:any):boolean{
	return val==null||
		typeof val==='boolean'||
		typeof val==='number'||
		typeof val==='string'||
		typeof val==='function'||
		typeof val==='bigint'||
		val.constructor===Symbol;
}
/**
 * 数组或对象直接覆盖
 */
class CopyCover{
	public value;
	constructor(value:unknown){
		this.value=value;
	}
}
function copyCover(array:unknown){
	return new CopyCover(array);
}
/**
 * 替换数组, 在调用copy方法时用于替换, 而不是扩展
 */
class ReplaceArr{
	public value;
	constructor(value:Array<any>){
		this.value=value;
	}
}
/**
 * 替换数组生成器
 * @param array 数组
 */
function replaceArr(array:Array<any>){
	return new ReplaceArr(array);
}
/**
 * 深度拷贝
 * dest,src要么同为数组, 要么同为对象
 * @param dest 目标对象或数组, 会被改变
 * @param src 被拷贝的对象或数组
 */
function copy(dest:PlainObject,src:PlainObject):void;
/**
 * 深度拷贝
 * dest,src要么同为数组, 要么同为对象
 * @param dest 目标对象或数组, 会被改变
 * @param src 被拷贝的对象或数组
 */
// eslint-disable-next-line no-redeclare
function copy(dest:Array<any>,src:Array<any>|ReplaceArr):void;
/**
 * 深度拷贝
 * dest,src要么同为数组, 要么同为对象
 * @param dest 目标对象或数组, 会被改变
 * @param src 被拷贝的对象或数组
 */
// eslint-disable-next-line no-redeclare
function copy(dest:PlainObject|Array<any>,src:PlainObject|Array<any>|ReplaceArr):void{
	if(Array.isArray(src)){
		const len=dest.length;
		// dest,src都是数组,新增模式
		for(let i=0;i<src.length;++i){
			_copyInner(dest,src[i],len+i);
		}
	}else if(src.constructor===ReplaceArr){
		// dest,src都是数组,替换模式
		const array=src.value;
		for(let i=0;i<array.length;++i){
			_copyInner(dest as any[],array[i],i);
		}
	}else{
		// dest,src都是对象
		const keys=Object.keys(src);
		for(let i=0;i<keys.length;++i){
			// @ts-ignore
			_copyInner(dest,src[keys[i]],keys[i]);
		}
	}
}
/**
 * 替换指定键的值
 * @param dest 目标对象或数组, 会被改变
 * @param value 用于替换的值
 * @param key 被替换的键
 */
function _copyInner(dest:PlainObject,value:any,key:string):void;
// eslint-disable-next-line no-redeclare
function _copyInner(dest:Array<any>,value:any,key:number):void;
// eslint-disable-next-line no-redeclare
function _copyInner(dest:PlainObject|Array<any>,value:any,key:string|number):void{
	let valConstructor;
	if(isBaseType(value)){
		// @ts-ignore
		dest[key]=value;
	}else if(value instanceof Date){
		// @ts-ignore
		dest[key]=new Date(value);
	}else if(value instanceof RegExp){
		// @ts-ignore
		const regexp=new RegExp(value.source,/\w*$/.exec(value));
		regexp.lastIndex=value.lastIndex;
		// @ts-ignore
		dest[key]=regexp;
	}else if(value instanceof Set||value instanceof Map){
		// @ts-ignore
		dest[key]=new value.constructor(value);
	}else if(value instanceof MySet){
		// @ts-ignore
		dest[key]=new MySet(value);
	}else if((valConstructor=value.constructor)===CopyCover){
		// @ts-ignore
		dest[key]=value.value;
	}else{
		// 被拷贝值为数组或对象
		// @ts-ignore
		const oldValue=dest[key];
		if(
			isBaseType(oldValue)||
			(
				(Array.isArray(value)||valConstructor===ReplaceArr)!==Array.isArray(oldValue)
			)
		){
			/**
			 * 当以下情况时丢弃原有值:
			 * 1.原有值为基本类型
			 * 2.原有值和被拷贝值,类型不相同时
			 */
			// @ts-ignore
			dest[key]=valConstructor===ReplaceArr?[]:new value.constructor();
		}
		// @ts-ignore
		return copy(dest[key],value);
	}
}
/**
 * 深度拷贝
 * 合并多个对象(深度拷贝), 后面覆盖前面, 会改变第一个对象, 支持: Date, RegExp, Set, Map等类型的拷贝
 * @example 常用情况
 * const target1={a:1,b:2};
 * extend(target1,{a:2,c:3}); //{a:2,b:2,c:3}
 * @example 追加数组元素
 * const target2=[1,2,3];
 * extend(target2,[4,5]); //[1,2,3,4,5]
 * @example 替换数组元素
 * const target3=[1,2,3];
 * extend(target3,replaceArr([4,5])); //[4,5,3]
 * @example 支持多个对象合并,且以上情况的任意嵌套,组合
 * const target4={a:1,b:2,c:[1,2,3],d:[1,2,3]};
 * extend(target4,{a:2,f:10},{c:[4,5]},{d:replaceArr([4,5])});
 * //{a:2,b:2,c:[1,2,3,4,5],d:[4,5,3],f:10}
 * @param target 目标对象或数组, 会被改变
 * @param srcs 被拷贝的对象或数组
 * @return 第一个参数
 */
function extend(target:Array<any>,...srcs:Array<Array<any>|ReplaceArr>):Array<any>;
/**
 * 深度拷贝
 * 合并多个对象(深度拷贝), 后面覆盖前面, 会改变第一个对象, 支持: Date, RegExp, Set, Map等类型的拷贝
 * @example 常用情况
 * const target1={a:1,b:2};
 * extend(target1,{a:2,c:3}); //{a:2,b:2,c:3}
 * @example 追加数组元素
 * const target2=[1,2,3];
 * extend(target2,[4,5]); //[1,2,3,4,5]
 * @example 替换数组元素
 * const target3=[1,2,3];
 * extend(target3,replaceArr([4,5])); //[4,5,3]
 * @example 支持多个对象合并,且以上情况的任意嵌套,组合
 * const target4={a:1,b:2,c:[1,2,3],d:[1,2,3]};
 * extend(target4,{a:2,f:10},{c:[4,5]},{d:replaceArr([4,5])});
 * //{a:2,b:2,c:[1,2,3,4,5],d:[4,5,3],f:10}
 * @param target 目标对象或数组, 会被改变
 * @param srcs 被拷贝的对象或数组
 * @return 第一个参数
 */
// eslint-disable-next-line no-redeclare
function extend(target:PlainObject,...srcs:Array<PlainObject>):PlainObject;
/**
 * 深度拷贝
 * 合并多个对象(深度拷贝), 后面覆盖前面, 会改变第一个对象, 支持: Date, RegExp, Set, Map等类型的拷贝
 * @example 常用情况
 * const target1={a:1,b:2};
 * extend(target1,{a:2,c:3}); //{a:2,b:2,c:3}
 * @example 追加数组元素
 * const target2=[1,2,3];
 * extend(target2,[4,5]); //[1,2,3,4,5]
 * @example 替换数组元素
 * const target3=[1,2,3];
 * extend(target3,replaceArr([4,5])); //[4,5,3]
 * @example 支持多个对象合并,且以上情况的任意嵌套,组合
 * const target4={a:1,b:2,c:[1,2,3],d:[1,2,3]};
 * extend(target4,{a:2,f:10},{c:[4,5]},{d:replaceArr([4,5])});
 * //{a:2,b:2,c:[1,2,3,4,5],d:[4,5,3],f:10}
 * @param target 目标对象或数组, 会被改变
 * @param srcs 被拷贝的对象或数组
 * @return 第一个参数
 */
// eslint-disable-next-line no-redeclare
function extend(target:Array<any>|PlainObject,...srcs:Array<Array<any>|ReplaceArr|PlainObject>):Array<any>|PlainObject{
	if(isBaseType(target)){
		throw new TypeError('target必须是对象或数组!');
	}
	const isArray=Array.isArray(target);
	for(let i=0;i<srcs.length;++i){
		const src=srcs[i];
		/**
		 * 当以下情况时忽略拷贝:
		 * 1.被拷贝对象为基本类型时
		 * 2.被拷贝对象和目标对象,类型不相同时
		 */
		if(
			isBaseType(src)||
			(
				(Array.isArray(src)||src instanceof ReplaceArr)!==isArray
			)
		) continue;
		copy(target,src);
	}
	return target;
}
/**
 * 返回转换为指定实例的类型的转换器
 * @param type 目标类型的实例
 * @return 转换器
 */
function convert(type:any){
	if(typeof type==='boolean'){
		return function(val:unknown):boolean{
			if(typeof val==='string'&&val.toLowerCase()==='false') return false;
			return Boolean(val);
		};
	}else if(typeof type==='number'){
		return function(val:unknown):number{
			if(typeof val==='string'&&val){
				val=Number(val);
			}
			if(typeof val==='number'&&Number.isFinite(val)){
				return val;
			}
			return type;
		};
	}else if(typeof type==='string'){
		return function(val:unknown):string{
			return val+'';
		};
	}else{
		return function(val:unknown):unknown{
			return val;
		};
	}
}
export interface TemplateNewKeyFn{
	(oldKey:string):string;
}
/**
 * defaultTemplate方法, 更改对象的键名
 */
class TemplateRename{
	public key:string|TemplateNewKeyFn;
	public def:any;
	constructor(key:string|TemplateNewKeyFn,def:any){
		this.key=key;
		this.def=def;
	}
}
function templateRename(key:string|TemplateNewKeyFn,def:any){
	return new TemplateRename(key,def);
}
export interface DefaultTemplateOption{
	//Object的通用模板键
	allTemplateKey?:string;
}
export interface InnerDefaultTemplateOption{
	objectFn:(def:any,src:any,opt:InnerDefaultTemplateOption) => any;
}
/**
 * defaultTemplate方法, 获取真正的 key 和 def
 */
function _setObjectKey(obj:any,src:any,opt:InnerDefaultTemplateOption,owner:any,def:any,key:any):void{
	if(def!=null&&def.constructor===TemplateRename){
		key=typeof def.key==='function'?def.key(key):def.key;
		def=def.def;
	}
	const value=_defaultTemplate(def,src,opt,owner);
	if(value!==undefined){
		obj[key]=value;
	}
}
/**
 * 默认值模板
 * 定义src的默认值(当提供的数据为null或undefined则启用默认值), 且自动转换为默认值的类型. 会删除原始数据有且模板中不存在的属性, 添加模板上有且原始数据不存在的属性
 * @example 基本类型
 * defaultTemplate(10,undefined); //10
 * defaultTemplate(10,20); //20
 * defaultTemplate(10,'20'); //20
 * defaultTemplate(false,null); //false
 * defaultTemplate(false,true); //true
 * defaultTemplate(false,'true'); //true
 * @example 原样返回
 * defaultTemplate(null,10); //10
 * defaultTemplate(null,'10'); //'10'
 * defaultTemplate(null,{a:1}); //{a:1}
 * @example 数组默认值
 * defaultTemplate([10],[null,'20',20]); //[10,20,20]
 * @example 对象默认值
 * defaultTemplate({a:10,b:false,c:20},{a:'20',b:null,d:'test'}); //{a:20,b:false,c:20}
 * defaultTemplate({a:10,'?':true},{b:'false',c:null,d:false}); //{a:10,b:false,c:true,d:false}
 * @example 自定义处理函数
 * defaultTemplate(function(data){return data*2;},20); //40
 * @param def	模板, null或undefined表示原样返回, 数组中的对象表示对每个元素的模板, 对象中的 " ? " 键表示适配所有键
 * @param src	原数据
 * @param opt	选项
 * @param opt.allTemplateKey	Object的通用模板键
 * @return 处理后的数据
 */
function defaultTemplate(def:any,src:any,opt:DefaultTemplateOption={allTemplateKey:'?'}):any{
	return _defaultTemplate(def,src,{
		objectFn:opt.allTemplateKey
			//region 启用对象通用模板
			?function(allTemplateKey:string,def:any,src:any,opt:InnerDefaultTemplateOption){
				const obj:PlainObject={};
				const defKeys=Object.keys(def);
				if(isObject(src)){
					if(allTemplateKey in def){
						const allTemplateDef=def[allTemplateKey];
						for(const key of Object.keys(src)){
							_setObjectKey(obj,src[key],opt,src,key in def?def[key]:allTemplateDef,key);
						}
						for(const key of defKeys){
							if(key===allTemplateKey||Object.prototype.hasOwnProperty.call(src,key)) continue;
							_setObjectKey(obj,undefined,opt,src,def[key],key);
						}
					}else{
						for(const key of defKeys){
							_setObjectKey(obj,src[key],opt,src,def[key],key);
						}
					}
				}else{
					for(const key of defKeys){
						if(key===allTemplateKey) continue;
						_setObjectKey(obj,undefined,opt,src,def[key],key);
					}
				}
				return obj;
			}.bind(null,opt.allTemplateKey)
			//endregion
			//region 禁用对象通用模板
			:function(def:any,src:any,opt:any){
				const obj:PlainObject={};
				for(const key of Object.keys(def)){
					_setObjectKey(obj,src==null||Array.isArray(src)?undefined:src[key],opt,src,def[key],key);
				}
				return obj;
			},
		//endregion
	},undefined);
}
function _defaultTemplate(def:any,src:any,opt:InnerDefaultTemplateOption,owner:any):any{
	if(def==null){
		return src;
	}else if(typeof def==='function'){
		return def(src,owner);
	}else if(isBaseType(def)){
		return src!=null&&isBaseType(src)?convert(def)(src):def;
	}else if(Array.isArray(def)){
		if(Array.isArray(src)){
			if(def.length){
				const arr=new Array(src.length);
				const def1=def[0];
				for(let i=0;i<src.length;++i){
					arr[i]=_defaultTemplate(def1,src[i],opt,src);
				}
				return arr;
			}
			return src;
		}
		return [];
	}else{
		return opt.objectFn(def,src,opt);
	}
}
/**
 * 把值转换为字符串(可直接eval的字符串)
 * @param value 任意值
 * @return 值的字符串表示
 */
function toPlainString(value:any):string{
	if(isObject(value)){
		const keys=Object.keys(value);
		if(keys.length){
			let result='{';
			for(let i=0;i<keys.length-1;++i){
				result+=keys[i]+':'+toPlainString(value[keys[i]])+',';
			}
			return result+keys[keys.length-1]+':'+toPlainString(value[keys[keys.length-1]])+'}';
		}
		return '{}';
	}else if(Array.isArray(value)){
		if(value.length){
			let result='[';
			for(let i=0;i<value.length-1;++i){
				result+=toPlainString(value[i])+',';
			}
			return result+toPlainString(value[value.length-1])+']';
		}
		return '[]';
	}else if(typeof value==='string'){
		return JSON.stringify(value);
	}else{
		return value+'';
	}
}
/**
 * 存储数据
 * 向localStorage存储数据
 * @param key 存储的键名
 * @param value 需要存储的数据
 * @param [storeFn] 是否存储函数
 */
function storageSet(key:string,value:any,storeFn?:boolean):void{
	if(storeFn){
		localStorage.setItem(key,'"use strict";return '+toPlainString(value));
	}else{
		localStorage.setItem(key,JSON.stringify(value));
	}
}
/**
 * 获取数据
 * 向localStorage获取已经存储的数据
 * @param key 存储的键名
 * @param [storeFn] 是否存储函数
 * @return 存储的数据
 */
function storageGet(key:string,storeFn?:boolean):any{
	const val=localStorage.getItem(key);
	if(val===null) return null;
	if(val==='undefined') return undefined;
	if(storeFn){
		// eslint-disable-next-line no-new-func
		return Function(val)();
	}else{
		return JSON.parse(val);
	}
}
/**
 * 删除数据
 * 向localStorage删除已经存储的数据
 * @param key 存储的键名
 */
function storageDel(key:string):void{
	localStorage.removeItem(key);
}
/**
 * 宽度描述
 */
export type WidthDescribe=undefined|number|string|WidthLimit;
/**
 * 含有fr列的信息
 * @property {number} index			列索引
 * @property {string|number} key	原始宽度函数键名
 * @property {number} count			fr的数量
 * @property {boolean} [disable]	是否被禁用
 */
export interface FrInfo{
	index:number,
	key:string|number,
	count:number,
	disable?:boolean,
}
/**
 * 列宽表达式(返回宽度列表生成器)
 * 通过列宽表达式列表, 返回宽度列表生成器, 表达式中, 数字表示固定宽度px, fr表示剩余宽度的权重(只限正整数), 支持百分数, 加减乘除运算
 * @example 不限宽模式
 * const calcColumnWidthFn=getCalcColumnWidthFn([100,'10%+50','1fr','2fr','1fr+10%+50']);
 * calcColumnWidthFn(1000); //[100, 150, 150, 300, 300]
 * @example 限宽模式
 * const calcColumnWidthFn=getCalcColumnWidthFn([100,'10%+50','1fr','2fr',{width:'1fr+10%+50',maxWidth:200}]);
 * calcColumnWidthFn(1000); //[100, 150, 183, 367, 200]
 * @param columnWidths 列宽表达式
 * @return 宽度列表生成器
 */
function getCalcColumnWidthFn(columnWidths:Array<WidthDescribe>):(width:number)=>Array<number>{
	const widthOrFnArr=new Array(columnWidths.length);
	let getFnsStr='return {';
	let allSumStr='0';
	//所有含有fr的列
	const frArr:Array<FrInfo>=[];
	//有一列宽度被限制
	let widthLimited:boolean;
	//除去固定宽度, 剩余的宽度
	let restWidth:number;
	//剩下的fr数量
	let curFrCount:number;
	for(let i=0;i<columnWidths.length;++i){
		let arg=columnWidths[i];
		if(typeof arg==='number'){
			arg=Math.round(arg>0?arg:0);
			widthOrFnArr[i]=arg;
		}else if(typeof arg==='string'||arg==null){
			arg=(arg||'1fr').replace(/(\d+(?:\.\d+)?)(?:%|(fr))/g,'$1*$2width');
			if(arg.indexOf('fr')!==-1){
				frArr.push({
					index:i,
					key:i,
					count:0,
				});
			}
			getFnsStr+=i+':function(allWidth,frwidth){var width=allWidth/100;var res='+arg+';return Math.round(res>0?res:0)},';
		}else{
			const {width,minWidth,maxWidth}=arg;
			if(typeof width==='number'){
				arg=width>0?width:0;
				if(minWidth&&minWidth>arg){
					arg=minWidth;
				}
				if(maxWidth&&maxWidth<arg){
					arg=maxWidth;
				}
				widthOrFnArr[i]=Math.round(arg);
			}else{
				arg=(width||'1fr').replace(/(\d+(?:\.\d+)?)(?:%|(fr))/g,'$1*$2width');
				const key='a'+i;
				let info:FrInfo;
				if(arg.indexOf('fr')!==-1){
					info={
						index:i,
						key,
						count:0,
					};
					frArr.push(info);
				}
				getFnsStr+=key+':function(allWidth,frwidth){var width=allWidth/100;var res='+arg+';return Math.round(res>0?res:0)},';
				widthOrFnArr[i]=function(allWidth:number,frwidth:number){
					let ideaWidth=allFnObj[key](allWidth,frwidth);
					let limit=false;
					if(minWidth&&minWidth>ideaWidth){
						ideaWidth=minWidth;
						limit=true;
					}
					if(maxWidth&&maxWidth<ideaWidth){
						ideaWidth=maxWidth;
						limit=true;
					}
					if(info){
						if(limit){
							info.disable=true;
							restWidth+=allFnObj[key](allWidth,0)-ideaWidth;
							widthLimited=true;
							curFrCount-=info.count;
						}else{
							info.disable=false;
						}
					}
					return ideaWidth;
				};
			}
		}
		allSumStr+='+'+arg;
	}
	getFnsStr+='allSum:function(allWidth,frwidth){var width=allWidth/100;return '+allSumStr+'}}';
	// eslint-disable-next-line no-new-func
	const allFnObj=Function(getFnsStr)();
	for(const key in allFnObj){
		if(key[0]==='a') continue;
		// @ts-ignore
		widthOrFnArr[key]=allFnObj[key];
	}
	const allSum=allFnObj.allSum;
	let frCount=0;
	for(let i=0;i<frArr.length;++i){
		const rawFn=allFnObj[frArr[i].key];
		const count=rawFn(0,1)-rawFn(0,0);
		frArr[i].count=count;
		frCount+=count;
	}
	return function(width){
		//重置全局变量
		curFrCount=frCount;
		widthLimited=false;
		width=Math.round(width);
		let remain;
		if(curFrCount){
			restWidth=width-allSum(width,0);
			remain=restWidth/curFrCount;
			remain=remain>0?remain:0;
		}else{
			remain=0;
		}
		const res=new Array(widthOrFnArr.length);
		for(let i=0;i<widthOrFnArr.length;++i){
			const ele=widthOrFnArr[i];
			if(typeof ele==='number'){
				res[i]=ele;
			}else{
				res[i]=ele(width,remain);
			}
		}
		// eslint-disable-next-line no-unmodified-loop-condition
		while(widthLimited){
			widthLimited=false;
			if(curFrCount){
				remain=restWidth/curFrCount;
				remain=remain>0?remain:0;
				for(let i=0;i<frArr.length;++i){
					const {index,disable}=frArr[i];
					if(disable!==true){
						res[index]=widthOrFnArr[index](width,remain);
					}
				}
			}else{
				remain=0;
			}
		}
		let miss=width-res.reduce(function(a,b){
			return a+b;
		},0);
		//有剩余宽度,且计算总宽度和传入宽度不一致(需要抹平)
		if(remain&&miss){
			const step=miss/Math.abs(miss);
			let cur=0;
			while(miss){
				if(!frArr[cur].disable){
					res[frArr[cur].index]+=step;
					miss-=step;
				}
				++cur;
				if(cur===frArr.length){
					cur=0;
				}
			}
		}
		return res;
	};
}
const onceActionMap:PromiseMap={};
/**
 * 只执行一次的Action
 * 用于包裹vuex的Action, 使其只执行一次, 注意: 调用时可以传参immediate为true标识立即执行
 * @param map 原始的Action键值对
 * @return 处理后的Action键值对
 */
function vuexActionOnce(map:ActionMap):ActionMap{
	const result:ActionMap={};
	const keys=Object.keys(map);
	for(let i=0;i<keys.length;++i){
		const key=keys[i];
		const val=map[key];
		result[key]=function(...args:Array<any>){
			const immediate=args[1]?.immediate;
			if(!immediate&&onceActionMap[key]) return onceActionMap[key];
			onceActionMap[key]=val(...args);
			return onceActionMap[key];
		};
	}
	return result;
}
/**
 * 转换为长度字符串, 没有单位时默认为px
 * @param length 长度
 * @return 带单位的长度值
 */
function convertCssLength(length:number|string):string{
	if(typeof length==='number'){
		return length+'px';
	}else{
		const convert=Number(length);
		if(isNaN(convert)){
			return length;
		}else{
			return convert+'px';
		}
	}
}
/**
 * 遍历树结构
 * 遍历树结构, 可提前退出, 可检索祖先节点
 * @example
loopTree(tree1,function({node,parent}){
    console.log('遍历:',node.name);
    if(node.name==='节点1'){
        console.log('父节点:',parent.node.name);
        //提前退出遍历
        return true;
    }
},'sons');
 * @param {Array<*>} tree 						需要遍历的树
 * @param {TreeNodeCallback} callback			每个节点的回调函数, 返回值可以指示, 删除节点, 跳过节点, 终止遍历
 * @param {string} [childrenAttr='children']	节点的子节点数组属性
 * @return {Array<*>}							被修改后的树
 */
function loopTree(tree:Array<any>,callback:TreeNodeCallback,childrenAttr='children'):Array<any>{
	const standardTree={[childrenAttr]:tree};
	innerLoopTree(tree,callback,childrenAttr,standardTree);
	return standardTree[childrenAttr];
}
/**
 * 遍历树结构(内部)
 * @param {Array<*>} tree				需要遍历的树
 * @param {TreeNodeCallback} callback	每个节点的回调函数, 返回值可以指示, 删除节点, 跳过节点, 终止遍历
 * @param {string} childrenAttr			节点的子节点数组属性
 * @param standardTree					树(用于修改)
 * @param {TreeNodeWrap} [parent]		父节点包装对象
 * @return 是否提前退出遍历
 */
function innerLoopTree(
	tree:Array<any>,
	callback:TreeNodeCallback,
	childrenAttr:string,
	standardTree:{[k:string]:Array<any>},
	parent?:TreeNodeWrap
):boolean{
	let hasRemove=false;
	let hasStop=false;
	for(let i=0;i<tree.length;++i){
		const node=tree[i];
		const nodeWrap={node,parent};
		const state=callback(nodeWrap);
		if(state){
			if(state===true){
				hasStop=true;
				break;
			}else{
				if(state.remove){
					tree[i]=null;
					hasRemove=true;
				}
				if(state.stop){
					hasStop=true;
					break;
				}else if(state.skip||state.remove){
					continue;
				}
			}
		}
		const children=node[childrenAttr];
		if(children&&innerLoopTree(children,callback,childrenAttr,node,nodeWrap)){
			hasStop=true;
			break;
		}
	}
	if(hasRemove){
		standardTree[childrenAttr]=tree.filter((n) => n!==null);
	}
	return hasStop;
}
/**
 * 获取小数位数
 * 传入一个实数, 返回该实数的小数位数, Infinity, NaN, undefined会返回-1
 * @example 传数字类型
 * decimalDigits(1.23); //2
 * decimalDigits(1.2300); //2
 * decimalDigits(1.23e-100); //102
 * @example 传字符串类型
 * decimalDigits('1.23'); //2
 * decimalDigits('1.2300'); //2
 * decimalDigits('1.23e-100'); //102
 * @param {number|string} number	传入的实数
 * @return {number}					小数位数
 */
function decimalDigits(number:number|string):number{
	if(typeof number==='string'){
		number=Number(number);
	}
	if(!isFinite(number)){
		return -1;
	}
	number=number+'';
	const p1=number.indexOf('.');
	const p2=number.indexOf('e');
	if(p2!==-1){
		let decimal=p1===-1?0:p2-p1-1;
		decimal-=Number(number.slice(p2+1));
		return decimal<0?0:decimal;
	}
	return p1===-1?0:number.length-p1-1;
}
/**
 * 四舍五入
 * 把指定实数, 按一定小数位数四舍五入
 * @example
 * round(3.141); //3
 * round(3.141,2); //3.14
 * @param {number} number	被四舍五入的实数
 * @param {number} digits	保留的小数位数
 * @return {number}			四舍五入后的值
 */
function round(number:number,digits?:number):number{
	if(digits){
		const pow=Math.pow(10,digits);
		return Math.round(number*pow)/pow;
	}
	return Math.round(number);
}
/**
 * 安全加法
 * 浮点数的加减不会出现误差, 例如:1 - 0.9
 * @example
 * add(1,-0.9); //0.1
 * @param {number} p1	相加数1
 * @param {number} p2	相加数2
 * @return {number}		和
 */
function add(p1:number,p2:number):number{
	const power=Math.pow(10,Math.max(0,decimalDigits(p1),decimalDigits(p2)));
	return Math.round((p1+p2)*power)/power;
}
/**
 * 安全加法(多个)
 * 浮点数的加减不会出现误差, 例如:1 - 0.9 + 1.2
 * @example
 * adds(1,-0.9,1.2); //1.3
 * @param {Array<number>} params	相加数
 * @return {number}					和
 */
function adds(...params:Array<number>):number{
	let maxDecimalDigits=0;
	let result=0;
	for(let i=0;i<params.length;++i){
		const val=decimalDigits(params[i]);
		result+=params[i];
		if(val>maxDecimalDigits){
			maxDecimalDigits=val;
		}
	}
	const power=Math.pow(10,maxDecimalDigits);
	return Math.round(result*power)/power;
}
/**
 * 安全乘法
 * 浮点数的乘除不会出现误差, 例如:0.1 * 0.9
 * @example
 * multi(0.1,0.9); //0.09
 * @param {number} p1	相乘数1
 * @param {number} p2	相乘数2
 * @return {number}		乘积
 */
function multi(p1:number,p2:number):number{
	const power=Math.pow(10,decimalDigits(p1)+decimalDigits(p2));
	return Math.round(p1*p2*power)/power;
}
/**
 * 安全乘法(多个)
 * 浮点数的乘除不会出现误差, 例如:0.1 * 0.9 * 2
 * @example
 * multis(0.1,0.9,2); //0.18
 * @param {Array<number>} params	相乘数
 * @return {number}					乘积
 */
function multis(...params:Array<number>):number{
	let maxDecimalDigits=0;
	let result=1;
	for(let i=0;i<params.length;++i){
		maxDecimalDigits+=decimalDigits(params[i]);
		result*=params[i];
	}
	const power=Math.pow(10,maxDecimalDigits);
	return Math.round(result*power)/power;
}
let uidCount=0;
/**
 * 获取唯一键
 * 获取和当前时间相关的唯一键
 * @return {string} 唯一键
 */
function getUid():string{
	if(uidCount===Number.MAX_SAFE_INTEGER){
		uidCount=0;
	}
	++uidCount;
	return Date.now()+'-'+uidCount;
}
/**
 * 用于表单上的api
 * @property {Vue} vm					vue实例
 * @property {Function} setVm			设置vue实例
 * @property {Function} initComplete	初始化完成
 * @property {Function} runTask			vm实例被挂载后执行
 * @property {Function} runAfterInit	vm实例被挂载且初始化完成时, 执行
 */
export interface LexmisApi{
	vm?:Vue,
	setVm:(vm:Vue) => void,
	initComplete:() => void,
	runTask:(task:() => any) => Promise<any>,
	runAfterInit:(task:() => any) => Promise<any>,
}
/**
 * 获取用于表单上的api
 * 获取一个内部封装了一定执行机制的api
 * @return {LexmisApi} 用于表单上的api
 */
function newLexmisApi():LexmisApi{
	//初始化完成标志
	let hasInit=false;
	//runTask的任务列表
	let taskList:Array<() => any>=[];
	//runAfterInit的任务列表
	let afterInitTask:Array<() => any>=[];
	return {
		setVm(vm){
			if(!this.vm&&vm){
				this.vm=vm;
				for(let i=0;i<taskList.length;++i){
					taskList[i]();
				}
				taskList=[];
			}
		},
		//初始化完成后调用(需要自己手动调用)
		initComplete(){
			if(!hasInit){
				hasInit=true;
				for(let i=0;i<afterInitTask.length;++i){
					afterInitTask[i]();
				}
				afterInitTask=[];
			}
		},
		//vm实例被挂载后执行
		runTask(task){
			if(this.vm){
				return Promise.resolve(task.call(this));
			}else{
				return new Promise((resolve) => {
					taskList.push(() => {
						resolve(task.call(this));
					});
				});
			}
		},
		//vm实例被挂载且初始化完成时, 执行
		runAfterInit(task){
			return this.runTask(() => {
				if(hasInit){
					return Promise.resolve(task.call(this));
				}else{
					return new Promise((resolve) => {
						afterInitTask.push(() => {
							resolve(task.call(this));
						});
					});
				}
			});
		},
	};
}
export type ReadFileType='readAsArrayBuffer'|'readAsDataURL';
/**
 * 读取文件
 * @param file		指定文件
 * @param method	读取方式
 * @return {Promise<string|ArrayBuffer|null>}
 */
function readFile(file:Blob,method:ReadFileType):Promise<string|ArrayBuffer|null>{
	return new Promise(function(resolve){
		const reader=new FileReader();
		reader.onload=function(){
			resolve(reader.result);
		};
		reader[method](file);
	});
}
/**
 * 复制到剪贴板降级方式
 * @param {string} text	复制的文本
 * @return {boolean}	是否成功
 */
function copyToClipboardFallback(text:string):Promise<void>{
	return new Promise((resolve,reject) => {
		const area=document.createElement('textarea');
		area.value=text;
		area.contentEditable='true';
		area.style.position='fixed';
		document.body.appendChild(area);
		area.focus();
		area.select();
		const res=document.execCommand('copy');
		area.remove();
		if(res){
			resolve();
		}else{
			reject(res);
		}
	});
}
/**
 * 复制到剪贴板
 * @param {string} text 复制的文本
 * @return {Promise<void>}
 */
function copyToClipboard(text:string):Promise<void>{
	return navigator.clipboard!==undefined
		?navigator.clipboard.writeText(text).catch(() => {
			return copyToClipboardFallback(text);
		})
		:copyToClipboardFallback(text);
}
//region debounce
export interface DebounceOption{
	leading?:boolean;
	trailing?:boolean;
	maxWait?:number;
}
// @ts-ignore
export interface DebounceExtraProp<T extends((...args) => unknown)>{
	cancel:() => void;
	pending:() => boolean;
	flush:() => ReturnType<T>;
}
/**
 * 创建防抖函数(wait为null或0时, 只支持trailing, maxWait失效, 且为节流函数)
 * @param func		需要防抖的函数
 * @param [wait]	延时毫秒
 * @param [options]	选项
 * @return 防抖函数
 */
function debounce<T extends((...args:Array<any>) => unknown)>(func:T,wait?:number,options?:DebounceOption):T&DebounceExtraProp<T>{
	//region 执行参数
	let lastArgs:any;
	let lastThis:any;
	let result:any;
	let timerId:number|undefined;
	let lastCallTime:number|undefined;
	let lastInvokeTime=0;
	//endregion
	const useRAF:boolean=wait==null;
	const waitNum:number=wait||0;
	const leading:boolean=waitNum!==0&&(options&&options.leading)===true;
	const trailing:boolean=(options&&options.trailing)!==false;
	const maxWait=options&&('maxWait' in options)?Math.max(waitNum,options.maxWait||0):undefined;
	//region startTimer设置定时器
	function startTimer(pendingFunc:() => void,waitNum:number){
		if(useRAF){
			return requestAnimationFrame(pendingFunc);
		}
		return self.setTimeout(pendingFunc,waitNum);
	}
	//endregion
	//region cancelTimer关闭定时器
	function cancelTimer(id:number):void{
		if(useRAF){
			return self.cancelAnimationFrame(id);
		}else{
			return self.clearTimeout(id);
		}
	}
	//endregion
	//region shouldInvoke是否应该执行
	function shouldInvoke(time:number):false|'ready'|'max'{
		const sinceLastInvoke=time-lastInvokeTime;
		if((lastCallTime===undefined||time-lastCallTime>=waitNum)&&sinceLastInvoke>=waitNum){
			return 'ready';
		}else if(maxWait!==undefined&&sinceLastInvoke>=maxWait){
			return 'max';
		}
		return false;
	}
	//endregion
	//region remainingWait剩余需要等待的时间
	function remainingWait(time:number){
		const timeWaiting=waitNum-(time-lastCallTime!);
		return maxWait!==undefined
			?Math.min(timeWaiting,maxWait-(time-lastInvokeTime))
			:timeWaiting;
	}
	//endregion
	//region timerExpired判断执行或者延时执行
	function timerExpired(){
		const time=Date.now();
		if(shouldInvoke(time)){
			return trailingEdge(time);
		}else{
			timerId=startTimer(timerExpired,remainingWait(time));
		}
	}
	//endregion
	//region leadingEdge开头调用
	function leadingEdge(time:number){
		lastInvokeTime=time;
		timerId=startTimer(timerExpired,waitNum);
		return leading?invokeFunc(time):result;
	}
	//endregion
	//region trailingEdge结尾调用
	function trailingEdge(time:number){
		timerId=undefined;
		if(trailing&&lastArgs){
			return invokeFunc(time);
		}else{
			lastArgs=lastThis=undefined;
		}
		return result;
	}
	//endregion
	//region invokeFunc执行方法
	function invokeFunc(time:number){
		const args=lastArgs;
		const thisArg=lastThis;
		lastArgs=lastThis=undefined;
		lastInvokeTime=time;
		result=func.apply(thisArg,args);
		return result;
	}
	//endregion
	const debounced=function(this:any,...args){
		const time=Date.now();
		const isInvoking=shouldInvoke(time);
		lastArgs=args;
		lastThis=this;
		lastCallTime=time;
		if(isInvoking==='ready'){
			if(timerId===undefined){
				return leadingEdge(time);
			}
		}else if(isInvoking==='max'){
			return invokeFunc(time);
		}else if(timerId===undefined){
			timerId=startTimer(timerExpired,waitNum);
		}
		return result;
	} as T;
	//region 添加额外的方法cancel, flush, pending
	return Object.assign(debounced,{
		cancel():void{
			if(timerId!==undefined){
				cancelTimer(timerId);
			}
			lastInvokeTime=0;
			lastArgs=lastThis=timerId=lastCallTime=undefined;
		},
		flush():ReturnType<T>{
			if(timerId!==undefined){
				cancelTimer(timerId);
				return trailingEdge(Date.now());
			}else{
				return result;
			}
		},
		pending():boolean{
			return timerId!==undefined;
		},
	});
	//endregion
}
//endregion
//region throttle
export interface ThrottleOption{
	leading?:boolean;
	trailing?:boolean;
}
/**
 * 创建节流函数(wait为null或0时, 只支持trailing)
 * @param func		需要节流的函数
 * @param [wait]	间隔时间
 * @param [options]	选项
 * @return 节流函数
 */
function throttle<T extends((...args:Array<any>) => unknown)>(func:T,wait?:number,options?:ThrottleOption):T&DebounceExtraProp<T>{
	return debounce(func,wait,Object.assign({
		leading:true,
		trailing:true,
	},options,{
		maxWait:wait,
	}));
}
//endregion
//region noop
function noop(){}
//endregion
//region beforeProcessor
function beforeProcessor(status:any):Promise<void>{
	return Promise.resolve(status).then((success) => {
		if(success === false){
			throw new Error('前置处理器阻止');
		}
	});
}
//endregion
//region toCssLength
/**
 * 转换为长度字符串, 没有单位时默认为px
 * @param length 长度
 * @return 带单位的长度值
 */
function toCssLength(length:number | string | null | undefined):string | undefined{
	if(typeof length === 'number'){
		if(Number.isFinite(length)){
			return length + 'px';
		}else{
			return undefined;
		}
	}else if(typeof length === 'string'){
		const convert = Number(length);
		if(Number.isFinite(convert)){
			return convert + 'px';
		}else{
			return length;
		}
	}else{
		return undefined;
	}
}
//endregion
export * from './date';
export {
	//可以被vue观测的Set类型
	MySet,
	//判断普通对象(不包括数组)
	isObject,
	//判断基本类型(可以直接赋值)
	isBaseType,
	//深度拷贝
	copy,
	copyCover,
	//替换数组生成器
	replaceArr,
	//深度拷贝(后面覆盖前面,会影响第一个对象)
	extend,
	//返回转换为指定实例的类型的转换器
	convert,
	templateRename,
	//默认值模板
	defaultTemplate,
	//把值转换为字符串(可直接eval的字符串)
	toPlainString,
	//向localStorage存储任意数据
	storageSet,
	//向localStorage获取存储的数据
	storageGet,
	//删除localStorage的键
	storageDel,
	//列宽表达式(返回宽度列表生成器)
	getCalcColumnWidthFn,
	//用于包裹vuex的Action, 使其只执行一次
	vuexActionOnce,
	//转换为长度字符串, 没有单位时默认为px
	convertCssLength,
	//遍历树结构
	loopTree,
	//获取小数位数
	decimalDigits,
	//四舍五入
	round,
	//安全加法
	add,
	//安全加法(多个)
	adds,
	//安全乘法
	multi,
	//安全乘法(多个)
	multis,
	//获取唯一键
	getUid,
	//获取用于表单上的api
	newLexmisApi,
	//读取文件
	readFile,
	//复制到剪贴板
	copyToClipboard,
	debounce,
	throttle,
	noop,
	beforeProcessor,
	toCssLength,
};
